(function($) {    
    if ($ && $.jstree) {    //避免重复定义.
        return;
    }  
        
    //----------------------------------------------------------------------------------------------------------
      
    /*
     * jsTree core
     */    
    // Common functions not related to jsTree
    // decided to move them to a `vakata` "namespace"
    $.vakata = {};
    
    // private variables
    var instances = [], // instance array (used by $.jstree.reference/create/focused)
        focused_instance = -1, // the index in the instance array of the currently focused instance
        availablePlugins = {}, //存储所有的可用插件元数据.
        prepared_move = {}; // for the move_node function

    // jQuery plugin wrapper (thanks to jquery UI widget function)
    $.fn.jstree = function(settings) {
    	
        var isMethodCall = Type.isString(settings), // is this a method call like $('jsTreeHolderSelector').jstree("open_node")
            args = Array.prototype.slice.call(arguments, 1), 
            returnValue = this;

        // if a method call execute the method on all selected instances
        if (isMethodCall) {
            var methodName = settings;
            //不允许调用私有方法(以_开头的方法).
            if (methodName.startsWith('_')) {
                return returnValue;
            }            
            this.each(function() {
                var instance = instances[ $(this).data("jstree_instance_id") ], 
                    methodValue = (instance && $.isFunction(instance[methodName])) ? instance[methodName].apply(instance, args) : instance;
                if (!Type.isUndefined(methodValue) && (methodName.startsWith("is_") || !Type.isBoolean(methodValue))) {
                    returnValue = methodValue;
                    return false;
                }
            });
        } else {
            this.each(function() {
                // extend settings and allow for multiple hashes and $().data
                var $tree = $(this), //tree container.
                    instance_id = $tree.data("jstree_instance_id"), 
                    argsTmp = [].concat(args), //arguments.
                    treeConfigTmp = $.extend(true, {}, settings || {}), //clone settings.                    
                    treeConfig = false;
                
                if($tree.data("jstree")){
                    argsTmp.push($tree.data("jstree"));
                }
                if(argsTmp.length){
                    treeConfigTmp = $.extend.apply(null, [true, treeConfigTmp].concat(argsTmp));    
                }

                //若$tree上已存在jsTree实例，则先销毁之.
                if ( !Type.isUndefined(instance_id) && instances[instance_id]) {
                    instances[instance_id].destroy();
                }
                //生成新的jsTree实例ID.
                instance_id = parseInt(instances.push({}), 10) - 1;
                $tree.data("jstree_instance_id", instance_id);
                $tree.addClass("jstree jstree-" + instance_id);

                // clean up all plugins
                if(!$.isArray(treeConfigTmp.plugins)){
                    treeConfigTmp.plugins =  $.jstree.defaults.plugins.slice();    
                }                
                treeConfigTmp.plugins.unshift("core");
                treeConfigTmp.plugins.sort().unique();

                // extend defaults with passed data
                treeConfig = $.extend(true, {}, $.jstree.defaults, treeConfigTmp);                             
                //去除无效的plugins参数.
                $.each(availablePlugins, function(pluginName) {
                    if ($.inArray(pluginName, treeConfig.plugins) === -1) {                        
                        delete treeConfig[pluginName];
                        treeConfig.plugins.remove(pluginName);
                    }
                });                      
                //创建jsTree实例.          
                instances[instance_id] = new $.jstree.jsTreeConstructor(instance_id, $tree, treeConfig);

                //初始化指定的插件实例.
                $.each(treeConfig.plugins, function(index, pluginName) {
                    instances[instance_id].data[pluginName] = {};
                    if (availablePlugins[pluginName]) {
                        //插件的初始化方法 __init 在 jsTree实例上执行.                        
                        availablePlugins[pluginName].__init.apply( instances[instance_id] );
                    }
                });                
                // initialize the instance
                (function() {
                    if (instances[instance_id]) {
                        instances[instance_id].init();
                    }
                }).defer();
            });
        }
        // return the jquery selection (or if it was a method call that returned a value - the returned value)
        return returnValue;
    };

    // object to store exposed functions and objects
    $.jstree = {
        defaults : {
            plugins : []
        },
        /**
         * 获取当前jsTree实例. 
         */
        _focused : function() {
            return instances[focused_instance] || null;
        },
        /**
         * 获取指定的jsTree实例.  
         * @param {String} needle
         */
        getJsTreeObj : function(needle) {
            var jsTreeObj = null;
            if (instances[needle]) {
                jsTreeObj = instances[needle];
            } else {                
                var $tree = $(needle);
                $tree = !$tree.length && typeof needle === "string" ? $("#" + needle) : $tree;
                if($tree.length){
                    jsTreeObj = instances[ $tree.closest(".jstree").data("jstree_instance_id") ];
                }
            }
            return jsTreeObj;
        },
        /**
         * jsTree实例的构造函数. 
         */
        jsTreeConstructor : function(instance_id, $tree, treeConfig) {
            // for plugins to store data in
            this.data = {
                core : {}
            };     
            /**
             * 获取jsTree的配置副本. 
             */       
            this.get_settings = function() {
                return $.extend(true, {}, treeConfig);
            };
            /**
             * 获取jsTree的配置原本. 
             */
            this._get_settings = function() {
                return treeConfig;
            };
            /**
             * 获取 jsTree的实例ID. 
             */
            this.get_index = function() {
                return instance_id;
            };
            /**
             * 获取 jsTree的 $holder/$wrapper.
             */
            this.get_container = function() {
                return $tree;
            };
            /**
             * 获取所有节点的外层 ul 元素. 
             */
            this.get_container_ul = function() {
                return $tree.children("ul:eq(0)");
            };
            /**
             * 设置 jsTree实例参数. 
             */
            this._set_settings = function(config) {
                treeConfig = $.extend(true, {}, treeConfig, config);
            };
        },
        //jsTreeConstructor prototype
        _fn : { },
        /**
         * 插件的默认配置. 
         */
        _pluginDefaults : {
            __init : $.noop,
            __destroy : $.noop,
            _fn : {},
            defaults : false
        },
        /**
         * 插件注册. 
         * @param {String} pluginName 插件名称.
         * @param {Config} pluginConfig 插件配置.
         */
        plugin : function(pluginName, pluginConfig) {        	
            pluginConfig = $.extend({}, $.jstree._pluginDefaults, pluginConfig);            
            
            availablePlugins[pluginName] = pluginConfig;
            $.jstree.defaults[pluginName] = pluginConfig.defaults;            
            
            $.each(pluginConfig._fn, function(pluginMemberKey, pluginMemberValue) {                
                pluginMemberValue.plugin = pluginName;
                pluginMemberValue.old = $.jstree._fn[pluginMemberKey];                
                $.jstree._fn[pluginMemberKey] = function() {
                    var rslt, 
                        func = pluginMemberValue, 
                        args = Array.prototype.slice.call(arguments), 
                        evnt = new $.Event("before.jstree"), 
                        rlbk = false;
                        
                    if (this.data.core.locked === true && pluginMemberKey !== "unlock" && pluginMemberKey !== "is_locked") {
                        return;
                    }
                    
                    // Check if function belongs to the included plugins of this instance
                    do {
                        if (func && func.plugin && $.inArray(func.plugin, this._get_settings().plugins) !== -1) {
                            break;
                        }
                        func = func.old;
                    } while(func);
                    
                    if (!func) {
                        return;
                    }
                    
                    // context and function to trigger events, then finally call the function
                    if (pluginMemberKey.indexOf("_") === 0) {
                        rslt = func.apply(this, args);
                    } else {
                        rslt = this.get_container().triggerHandler(evnt, {
                            "func" : pluginMemberKey,
                            "inst" : this,
                            "args" : args,
                            "plugin" : func.plugin
                        });
                        if (rslt === false) { return; }
                        if ( typeof rslt !== "undefined") { args = rslt; }
                        rslt = func.apply($.extend({}, this, {
                            __callback : function(data) {
                                this.get_container().triggerHandler(pluginMemberKey + '.jstree', {
                                    "inst" : this,
                                    "args" : args,
                                    "rslt" : data,
                                    "rlbk" : rlbk
                                });
                            },
                            __rollback : function() {
                                rlbk = this.get_rollback();
                                return rlbk;
                            },
                            __call_old : function(replace_arguments) {
                                return func.old.apply(this, ( replace_arguments ? Array.prototype.slice.call(arguments, 1) : args ));
                            }
                        }), args);
                    }
                    // return the result
                    return rslt;
                };
                $.jstree._fn[pluginMemberKey].old = pluginMemberValue.old;
                $.jstree._fn[pluginMemberKey].plugin = pluginName;
            });
        },
        rollback : function(rb) {
            if (rb) {
                if (!$.isArray(rb)) {
                    rb = [rb];
                }
                $.each(rb, function(i, val) {
                    instances[val.i].set_rollback(val.h, val.d);
                });
            }
        }
    };
    // set the prototype for all instances
    $.jstree._fn = $.jstree.jsTreeConstructor.prototype = {};
    
    //-------------------------------------------------------------------------------------------------------------------------------------------

    // core functions (open, close, create, update, delete)
    $.jstree.plugin("core", {
        __init : function() {
            this.data.core.locked = false;
            this.data.core.to_open = this.get_settings().core.initially_open;
            this.data.core.to_load = this.get_settings().core.initially_load;
        },
        defaults : {
            html_titles : false,
            animation : 500,
            initially_open : [],
            initially_load : [],
            open_parents : true,
            notify_plugins : true,
            rtl : false,
            load_open : false,
            strings : {
                loading : "Loading ...",
                new_node : "New node",
                multiple_selection : "Multiple selection"
            }
        },
        _fn : {
            init : function() {
                this.set_focus();
                if (this._get_settings().core.rtl) {
                    this.get_container().addClass("jstree-rtl").css("direction", "rtl");
                }
                this.get_container().html("<ul><li class='jstree-last jstree-leaf'><ins>&#160;</ins><a class='jstree-loading' href='#'><ins class='jstree-icon'>&#160;</ins>" + this._get_string("loading") + "</a></li></ul>");
                this.data.core.li_height = this.get_container_ul().find("li.jstree-closed, li.jstree-leaf").eq(0).height() || 18;

                this.get_container().delegate("li > ins", "click.jstree", $.proxy(function(event) {
                    var trgt = $(event.target);                        
                    this.toggle_node(trgt);
                }, this)).bind("mousedown.jstree", $.proxy(function() {
                    this.set_focus();
                    // This used to be setTimeout(set_focus,0) - why?
                }, this)).bind("dblclick.jstree", function(event) {
                    var sel;
                    if (document.selection && document.selection.empty) {
                        document.selection.empty();
                    } else {
                        if (window.getSelection) {
                            sel = window.getSelection();
                            try {
                                sel.removeAllRanges();
                                sel.collapse();
                            } catch (err) {
                            }
                        }
                    }
                });
                if (this._get_settings().core.notify_plugins) {
                    this.get_container().bind("load_node.jstree", $.proxy(function(e, data) {
                        var o = this._get_node(data.rslt.obj), t = this;
                        if (o === -1) {
                            o = this.get_container_ul();
                        }
                        if (!o.length) {
                            return;
                        }
                        o.find("li").each(function() {
                            var th = $(this);
                            if (th.data("jstree")) {
                                $.each(th.data("jstree"), function(plugin, values) {
                                    if (t.data[plugin] && $.isFunction(t["_" + plugin + "_notify"])) {
                                        t["_" + plugin + "_notify"].call(t, th, values);
                                    }
                                });
                            }
                        });
                    }, this));
                }
                if (this._get_settings().core.load_open) {
                    this.get_container().bind("load_node.jstree", $.proxy(function(e, data) {
                        var o = this._get_node(data.rslt.obj), t = this;
                        if (o === -1) {
                            o = this.get_container_ul();
                        }
                        if (!o.length) {
                            return;
                        }
                        o.find("li.jstree-open:not(:has(ul))").each(function() {
                            t.load_node(this, $.noop, $.noop);
                        });
                    }, this));
                }
                this.__callback();
                this.load_node(-1, function() {
                    this.loaded();
                    this.reload_nodes();
                });
            },
            destroy : function() {
                var i, n = this.get_index(), s = this._get_settings(), _this = this;

                $.each(s.plugins, function(i, val) {
                    try {
                        availablePlugins[val].__destroy.apply(_this);
                    } catch(err) {
                    }
                });
                this.__callback();
                // set focus to another instance if this one is focused
                if (this.is_focused()) {
                    for (i in instances) {
                        if (instances.hasOwnProperty(i) && i != n) {
                            instances[i].set_focus();
                            break;
                        }
                    }
                }
                // if no other instance found
                if (n === focused_instance) {
                    focused_instance = -1;
                }
                // remove all traces of jstree in the DOM (only the ones set using jstree*) and cleans all events
                this.get_container().unbind(".jstree").undelegate(".jstree").removeData("jstree_instance_id").find("[class^='jstree']").andSelf().attr("class", function() {
                    return this.className.replace(/jstree[^ ]*|$/ig, '');
                });
                $(document).unbind(".jstree-" + n).undelegate(".jstree-" + n);
                // remove the actual data
                instances[n] = null;
                delete instances[n];
            },

            _core_notify : function(n, data) {
                if (data.opened) {
                    this.open_node(n, false, true);
                }
            },

            lock : function() {
                this.data.core.locked = true;
                this.get_container().children("ul").addClass("jstree-locked").css("opacity", "0.7");
                this.__callback({});
            },
            unlock : function() {
                this.data.core.locked = false;
                this.get_container().children("ul").removeClass("jstree-locked").css("opacity", "1");
                this.__callback({});
            },
            is_locked : function() {
                return this.data.core.locked;
            },
            save_opened : function() {
                var _this = this;
                this.data.core.to_open = [];
                this.get_container_ul().find("li.jstree-open").each(function() {
                    if (this.id) {
                        _this.data.core.to_open.push("#" + this.id.toString().replace(/^#/, "").replace(/\\\//g, "/").replace(/\//g, "\\\/").replace(/\\\./g, ".").replace(/\./g, "\\.").replace(/\:/g, "\\:"));
                    }
                });
                this.__callback(_this.data.core.to_open);
            },
            save_loaded : function() {},
            reload_nodes : function(is_callback) {
                var _this = this, done = true, current = [], remaining = [];
                if (!is_callback) {
                    this.data.core.reopen = false;
                    this.data.core.refreshing = true;
                    this.data.core.to_open = $.map($.makeArray(this.data.core.to_open), function(n) {
                        return "#" + n.toString().replace(/^#/, "").replace(/\\\//g, "/").replace(/\//g, "\\\/").replace(/\\\./g, ".").replace(/\./g, "\\.").replace(/\:/g, "\\:");
                    });
                    this.data.core.to_load = $.map($.makeArray(this.data.core.to_load), function(n) {
                        return "#" + n.toString().replace(/^#/, "").replace(/\\\//g, "/").replace(/\//g, "\\\/").replace(/\\\./g, ".").replace(/\./g, "\\.").replace(/\:/g, "\\:");
                    });
                    if (this.data.core.to_open.length) {
                        this.data.core.to_load = this.data.core.to_load.concat(this.data.core.to_open);
                    }
                }
                if (this.data.core.to_load.length) {
                    $.each(this.data.core.to_load, function(i, val) {
                        if (val == "#") {
                            return true;
                        }
                        if ($(val).length) {
                            current.push(val);
                        } else {
                            remaining.push(val);
                        }
                    });
                    if (current.length) {
                        this.data.core.to_load = remaining;
                        $.each(current, function(i, val) {
                            if (!_this._is_loaded(val)) {
                                _this.load_node(val, function() {
                                    _this.reload_nodes(true);
                                }, function() {
                                    _this.reload_nodes(true);
                                });
                                done = false;
                            }
                        });
                    }
                }
                if (this.data.core.to_open.length) {
                    $.each(this.data.core.to_open, function(i, val) {
                        _this.open_node(val, false, true);
                    });
                }
                if (done) {
                    // TODO: find a more elegant approach to syncronizing returning requests
                    if (this.data.core.reopen) {
                        clearTimeout(this.data.core.reopen);
                    }
                    this.data.core.reopen = setTimeout(function() {
                        _this.__callback({}, _this);
                    }, 50);
                    this.data.core.refreshing = false;
                    this.reopen();
                }
            },
            reopen : function() {
                var _this = this;
                if (this.data.core.to_open.length) {
                    $.each(this.data.core.to_open, function(i, val) {
                        _this.open_node(val, false, true);
                    });
                }
                this.__callback({});
            },
            refresh : function(obj, cb) {
                var _this = this;
                this.save_opened();
                if (!obj) {
                    obj = -1;
                }
                obj = this._get_node(obj);
                if (!obj) {
                    obj = -1;
                }
                if (obj !== -1) {
                    obj.children("UL").remove();
                } else {
                    this.get_container_ul().empty();
                }
                this.load_node(obj, function() {
                    _this.__callback({
                        "obj" : obj
                    });
                    _this.reload_nodes();
                    if (cb) {
                        cb();
                    }
                });
            },
            // Dummy function to fire after the first load (so that there is a jstree.loaded event)
            loaded : function() {
                this.__callback();
            },
            // deal with focus
            set_focus : function() {
                if (this.is_focused()) {
                    return;
                }
                var f = $.jstree._focused();
                if (f) {
                    f.unset_focus();
                }
                this.get_container().addClass("jstree-focused");
                focused_instance = this.get_index();
                this.__callback();
            },
            is_focused : function() {
                return focused_instance == this.get_index();
            },
            unset_focus : function() {
                if (this.is_focused()) {
                    this.get_container().removeClass("jstree-focused");
                    focused_instance = -1;
                }
                this.__callback();
            },

            // traverse
            _get_node : function(obj) {
                var $obj = $(obj, this.get_container());
                if ($obj.is(".jstree") || obj == -1) {
                    return -1;
                }
                $obj = $obj.closest("li", this.get_container());
                return $obj.length ? $obj : false;
            },
            _get_next : function(obj, strict) {
                obj = this._get_node(obj);
                if (obj === -1) {
                    return this.get_container().find("> ul > li:first-child");
                }
                if (!obj.length) {
                    return false;
                }
                if (strict) {
                    return (obj.nextAll("li").size() > 0) ? obj.nextAll("li:eq(0)") : false;
                }

                if (obj.hasClass("jstree-open")) {
                    return obj.find("li:eq(0)");
                } else if (obj.nextAll("li").size() > 0) {
                    return obj.nextAll("li:eq(0)");
                } else {
                    return obj.parentsUntil(".jstree", "li").next("li").eq(0);
                }
            },
            _get_prev : function(obj, strict) {
                obj = this._get_node(obj);
                if (obj === -1) {
                    return this.get_container().find("> ul > li:last-child");
                }
                if (!obj.length) {
                    return false;
                }
                if (strict) {
                    return (obj.prevAll("li").length > 0) ? obj.prevAll("li:eq(0)") : false;
                }

                if (obj.prev("li").length) {
                    obj = obj.prev("li").eq(0);
                    while (obj.hasClass("jstree-open")) {
                        obj = obj.children("ul:eq(0)").children("li:last");
                    }
                    return obj;
                } else {
                    var o = obj.parentsUntil(".jstree", "li:eq(0)");
                    return o.length ? o : false;
                }
            },
            _get_parent : function(obj) {
                obj = this._get_node(obj);
                if (obj == -1 || !obj.length) {
                    return false;
                }
                var o = obj.parentsUntil(".jstree", "li:eq(0)");
                return o.length ? o : -1;
            },
            _get_children : function(obj) {
                obj = this._get_node(obj);
                if (obj === -1) {
                    return this.get_container().children("ul:eq(0)").children("li");
                }
                if (!obj.length) {
                    return false;
                }
                return obj.children("ul:eq(0)").children("li");
            },
            get_path : function(obj, id_mode) {
                var p = [], _this = this;
                obj = this._get_node(obj);
                if (obj === -1 || !obj || !obj.length) {
                    return false;
                }
                obj.parentsUntil(".jstree", "li").each(function() {
                    p.push( id_mode ? this.id : _this.get_text(this));
                });
                p.reverse();
                p.push( id_mode ? obj.attr("id") : this.get_text(obj));
                return p;
            },

            // string functions
            _get_string : function(key) {
                return this._get_settings().core.strings[key] || key;
            },

            is_open : function(obj) {
                obj = this._get_node(obj);
                return obj && obj !== -1 && obj.hasClass("jstree-open");
            },
            is_closed : function(obj) {
                obj = this._get_node(obj);
                return obj && obj !== -1 && obj.hasClass("jstree-closed");
            },
            is_leaf : function(obj) {
                obj = this._get_node(obj);
                return obj && obj !== -1 && obj.hasClass("jstree-leaf");
            },
            correct_state : function(obj) {
                obj = this._get_node(obj);
                if (!obj || obj === -1) {
                    return false;
                }
                obj.removeClass("jstree-closed jstree-open").addClass("jstree-leaf").children("ul").remove();
                this.__callback({
                    "obj" : obj
                });
            },
            // open/close
            open_node : function(obj, callback, skip_animation) {
                obj = this._get_node(obj);
                if (!obj.length) {
                    return false;
                }
                if (!obj.hasClass("jstree-closed")) {
                    if (callback) {
                        callback.call();
                    }
                    return false;
                }
                var s = skip_animation || this._get_settings().core.animation, t = this;
                if (!this._is_loaded(obj)) {
                    obj.children("a").addClass("jstree-loading");
                    this.load_node(obj, function() {
                        t.open_node(obj, callback, skip_animation);
                    }, callback);
                } else {
                    if (this._get_settings().core.open_parents) {
                        obj.parentsUntil(".jstree", ".jstree-closed").each(function() {
                            t.open_node(this, false, true);
                        });
                    }
                    if (s) {
                        obj.children("ul").css("display", "none");
                    }
                    obj.removeClass("jstree-closed").addClass("jstree-open").children("a").removeClass("jstree-loading");
                    if (s) {
                        obj.children("ul").stop(true, true).slideDown(s, function() {
                            this.style.display = "";
                            t.after_open(obj);
                        });
                    } else {
                        t.after_open(obj);
                    }
                    this.__callback({
                        "obj" : obj
                    });
                    if (callback) {
                        callback.call();
                    }
                }
            },
            after_open : function(obj) {
                this.__callback({
                    "obj" : obj
                });
            },
            close_node : function(obj, skip_animation) {
                obj = this._get_node(obj);
                var s = skip_animation || this._get_settings().core.animation, t = this;
                if (!obj.length || !obj.hasClass("jstree-open")) {
                    return false;
                }
                if (s) {
                    obj.children("ul").attr("style", "display:block !important");
                }
                obj.removeClass("jstree-open").addClass("jstree-closed");
                if (s) {
                    obj.children("ul").stop(true, true).slideUp(s, function() {
                        this.style.display = "";
                        t.after_close(obj);
                    });
                } else {
                    t.after_close(obj);
                }
                this.__callback({
                    "obj" : obj
                });
            },
            after_close : function(obj) {
                this.__callback({
                    "obj" : obj
                });
            },
            toggle_node : function(obj) {
                obj = this._get_node(obj);
                if (obj.hasClass("jstree-closed")) {
                    return this.open_node(obj);
                }
                if (obj.hasClass("jstree-open")) {
                    return this.close_node(obj);
                }
            },
            open_all : function(obj, do_animation, original_obj) {
                obj = obj ? this._get_node(obj) : -1;
                if (!obj || obj === -1) {
                    obj = this.get_container_ul();
                }
                if (original_obj) {
                    obj = obj.find("li.jstree-closed");
                } else {
                    original_obj = obj;
                    if (obj.is(".jstree-closed")) {
                        obj = obj.find("li.jstree-closed").andSelf();
                    } else {
                        obj = obj.find("li.jstree-closed");
                    }
                }
                var _this = this;
                obj.each(function() {
                    var __this = this;
                    if (!_this._is_loaded(this)) {
                        _this.open_node(this, function() {
                            _this.open_all(__this, do_animation, original_obj);
                        }, !do_animation);
                    } else {
                        _this.open_node(this, false, !do_animation);
                    }
                });
                // so that callback is fired AFTER all nodes are open
                if (original_obj.find('li.jstree-closed').length === 0) {
                    this.__callback({
                        "obj" : original_obj
                    });
                }
            },
            close_all : function(obj, do_animation) {
                var _this = this;
                obj = obj ? this._get_node(obj) : this.get_container();
                if (!obj || obj === -1) {
                    obj = this.get_container_ul();
                }
                obj.find("li.jstree-open").andSelf().each(function() {
                    _this.close_node(this, !do_animation);
                });
                this.__callback({
                    "obj" : obj
                });
            },
            clean_node : function(obj) {
                obj = obj && obj != -1 ? $(obj) : this.get_container_ul();
                obj = obj.is("li") ? obj.find("li").andSelf() : obj.find("li");
                obj.removeClass("jstree-last").filter("li:last-child").addClass("jstree-last").end().filter(":has(li)").not(".jstree-open").removeClass("jstree-leaf").addClass("jstree-closed");
                obj.not(".jstree-open, .jstree-closed").addClass("jstree-leaf").children("ul").remove();
                this.__callback({
                    "obj" : obj
                });
            },
            // rollback
            get_rollback : function() {
                this.__callback();
                return {
                    i : this.get_index(),
                    h : this.get_container().children("ul").clone(true),
                    d : this.data
                };
            },
            set_rollback : function(html, data) {
                this.get_container().empty().append(html);
                this.data = data;
                this.__callback();
            },
            // Dummy functions to be overwritten by any datastore plugin included
            load_node : function(obj, s_call, e_call) {
                this.__callback({
                    "obj" : obj
                });
            },
            _is_loaded : function(obj) {
                return true;
            },

            // Basic operations: create
            create_node : function(obj, position, js, callback, is_loaded) {
                obj = this._get_node(obj);
                position = typeof position === "undefined" ? "last" : position;
                var d = $("<li />"), s = this._get_settings().core, tmp;

                if (obj !== -1 && !obj.length) {
                    return false;
                }
                if (!is_loaded && !this._is_loaded(obj)) {
                    this.load_node(obj, function() {
                        this.create_node(obj, position, js, callback, true);
                    });
                    return false;
                }

                this.__rollback();

                if ( typeof js === "string") {
                    js = {
                        "data" : js
                    };
                }
                if (!js) {
                    js = {};
                }
                if (js.attr) {
                    d.attr(js.attr);
                }
                if (js.metadata) {
                    d.data(js.metadata);
                }
                if (js.state) {
                    d.addClass("jstree-" + js.state);
                }
                if (!js.data) {
                    js.data = this._get_string("new_node");
                }
                if (!$.isArray(js.data)) {
                    tmp = js.data;
                    js.data = [];
                    js.data.push(tmp);
                }
                $.each(js.data, function(i, m) {
                    tmp = $("<a />");
                    if ($.isFunction(m)) {
                        m = m.call(this, js);
                    }
                    if ( typeof m == "string") {
                        tmp.attr('href','#')[ s.html_titles ? "html" : "text" ](m);
                    } else {
                        if (!m.attr) {
                            m.attr = {};
                        }
                        if (!m.attr.href) {
                            m.attr.href = '#';
                        }
                        tmp.attr(m.attr)[ s.html_titles ? "html" : "text" ](m.title);
                        if (m.language) {
                            tmp.addClass(m.language);
                        }
                    }
                    tmp.prepend("<ins class='jstree-icon'>&#160;</ins>");
                    if (!m.icon && js.icon) {
                        m.icon = js.icon;
                    }
                    if (m.icon) {
                        if (m.icon.indexOf("/") === -1) {
                            tmp.children("ins").addClass(m.icon);
                        } else {
                            tmp.children("ins").css("background", "url('" + m.icon + "') center center no-repeat");
                        }
                    }
                    d.append(tmp);
                });
                d.prepend("<ins class='jstree-icon'>&#160;</ins>");
                if (obj === -1) {
                    obj = this.get_container();
                    if (position === "before") {
                        position = "first";
                    }
                    if (position === "after") {
                        position = "last";
                    }
                }
                switch(position) {
                    case "before":
                        obj.before(d);
                        tmp = this._get_parent(obj);
                        break;
                    case "after" :
                        obj.after(d);
                        tmp = this._get_parent(obj);
                        break;
                    case "inside":
                    case "first" :
                        if (!obj.children("ul").length) {
                            obj.append("<ul />");
                        }
                        obj.children("ul").prepend(d);
                        tmp = obj;
                        break;
                    case "last":
                        if (!obj.children("ul").length) {
                            obj.append("<ul />");
                        }
                        obj.children("ul").append(d);
                        tmp = obj;
                        break;
                    default:
                        if (!obj.children("ul").length) {
                            obj.append("<ul />");
                        }
                        if (!position) {
                            position = 0;
                        }
                        tmp = obj.children("ul").children("li").eq(position);
                        if (tmp.length) {
                            tmp.before(d);
                        } else {
                            obj.children("ul").append(d);
                        }
                        tmp = obj;
                        break;
                }
                if (tmp === -1 || tmp.get(0) === this.get_container().get(0)) {
                    tmp = -1;
                }
                this.clean_node(tmp);
                this.__callback({
                    "obj" : d,
                    "parent" : tmp
                });
                if (callback) {
                    callback.call(this, d);
                }
                return d;
            },
            // Basic operations: rename (deal with text)
            get_text : function(obj) {
                obj = this._get_node(obj);
                if (!obj.length) {
                    return false;
                }
                var s = this._get_settings().core.html_titles;
                obj = obj.children("a:eq(0)");
                if (s) {
                    obj = obj.clone();
                    obj.children("INS").remove();
                    return obj.html();
                } else {
                    obj = obj.contents().filter(function() { return this.nodeType == 3; })[0];
                    return obj.nodeValue;
                }
            },
            set_text : function(obj, val) {
                obj = this._get_node(obj);
                if (!obj.length) {
                    return false;
                }
                obj = obj.children("a:eq(0)");
                if (this._get_settings().core.html_titles) {
                    var tmp = obj.children("INS").clone();
                    obj.html(val).prepend(tmp);
                    this.__callback({
                        "obj" : obj,
                        "name" : val
                    });
                    return true;
                } else {
                    obj = obj.contents().filter(function() { return this.nodeType == 3; })[0];
                    this.__callback({
                        "obj" : obj,
                        "name" : val
                    });
                    return (obj.nodeValue = val);
                }
            },
            rename_node : function(obj, val) {
                obj = this._get_node(obj);
                this.__rollback();
                if (obj && obj.length && this.set_text.apply(this, Array.prototype.slice.call(arguments))) {
                    this.__callback({
                        "obj" : obj,
                        "name" : val
                    });
                }
            },
            // Basic operations: deleting nodes
            delete_node : function(obj) {
                obj = this._get_node(obj);
                if (!obj.length) {
                    return false;
                }
                this.__rollback();
                var p = this._get_parent(obj), prev = $([]), t = this;
                obj.each(function() {
                    prev = prev.add(t._get_prev(this));
                });
                obj = obj.detach();
                if (p !== -1 && p.find("> ul > li").length === 0) {
                    p.removeClass("jstree-open jstree-closed").addClass("jstree-leaf");
                }
                this.clean_node(p);
                this.__callback({
                    "obj" : obj,
                    "prev" : prev,
                    "parent" : p
                });
                return obj;
            },
            prepare_move : function(o, r, pos, cb, is_cb) {
                var p = {};

                p.ot = $.jstree.getJsTreeObj(o) || this;
                p.o = p.ot._get_node(o);
                p.r = r === -1 ? -1 : this._get_node(r);
                p.p = ( typeof pos === "undefined" || pos === false) ? "last" : pos;
                // TODO: move to a setting
                if (!is_cb && prepared_move.o && prepared_move.o[0] === p.o[0] && prepared_move.r[0] === p.r[0] && prepared_move.p === p.p) {
                    this.__callback(prepared_move);
                    if (cb) {
                        cb.call(this, prepared_move);
                    }
                    return;
                }
                p.ot = $.jstree.getJsTreeObj(p.o) || this;
                p.rt = $.jstree.getJsTreeObj(p.r) || this;
                // r === -1 ? p.ot : $.jstree.getJsTreeObj(p.r) || this
                if (p.r === -1 || !p.r) {
                    p.cr = -1;
                    switch(p.p) {
                        case "first":
                        case "before":
                        case "inside":
                            p.cp = 0;
                            break;
                        case "after":
                        case "last":
                            p.cp = p.rt.get_container().find(" > ul > li").length;
                            break;
                        default:
                            p.cp = p.p;
                            break;
                    }
                } else {
                    if (!/^(before|after)$/.test(p.p) && !this._is_loaded(p.r)) {
                        return this.load_node(p.r, function() {
                            this.prepare_move(o, r, pos, cb, true);
                        });
                    }
                    switch(p.p) {
                        case "before":
                            p.cp = p.r.index();
                            p.cr = p.rt._get_parent(p.r);
                            break;
                        case "after":
                            p.cp = p.r.index() + 1;
                            p.cr = p.rt._get_parent(p.r);
                            break;
                        case "inside":
                        case "first":
                            p.cp = 0;
                            p.cr = p.r;
                            break;
                        case "last":
                            p.cp = p.r.find(" > ul > li").length;
                            p.cr = p.r;
                            break;
                        default:
                            p.cp = p.p;
                            p.cr = p.r;
                            break;
                    }
                }
                p.np = p.cr == -1 ? p.rt.get_container() : p.cr;
                p.op = p.ot._get_parent(p.o);
                p.cop = p.o.index();
                if (p.op === -1) {
                    p.op = p.ot ? p.ot.get_container() : this.get_container();
                }
                if (!/^(before|after)$/.test(p.p) && p.op && p.np && p.op[0] === p.np[0] && p.o.index() < p.cp) {
                    p.cp++;
                }
                //if(p.p === "before" && p.op && p.np && p.op[0] === p.np[0] && p.o.index() < p.cp) { p.cp--; }
                p.or = p.np.find(" > ul > li:nth-child(" + (p.cp + 1) + ")");
                prepared_move = p;
                this.__callback(prepared_move);
                if (cb) {
                    cb.call(this, prepared_move);
                }
            },
            check_move : function() {
                var obj = prepared_move, ret = true, r = obj.r === -1 ? this.get_container() : obj.r;
                if (!obj || !obj.o || obj.or[0] === obj.o[0]) {
                    return false;
                }
                if (obj.op && obj.np && obj.op[0] === obj.np[0] && obj.cp - 1 === obj.o.index()) {
                    return false;
                }
                obj.o.each(function() {
                    if (r.parentsUntil(".jstree", "li").andSelf().index(this) !== -1) {
                        ret = false;
                        return false;
                    }
                });
                return ret;
            },
            move_node : function(obj, ref, position, is_copy, is_prepared, skip_check) {
                if (!is_prepared) {
                    return this.prepare_move(obj, ref, position, function(p) {
                        this.move_node(p, false, false, is_copy, true, skip_check);
                    });
                }
                if (is_copy) {
                    prepared_move.cy = true;
                }
                if (!skip_check && !this.check_move()) {
                    return false;
                }

                this.__rollback();
                var o = false;
                if (is_copy) {
                    o = obj.o.clone(true);
                    o.find("*[id]").andSelf().each(function() {
                        if (this.id) {
                            this.id = "copy_" + this.id;
                        }
                    });
                } else {
                    o = obj.o;
                }

                if (obj.or.length) {
                    obj.or.before(o);
                } else {
                    if (!obj.np.children("ul").length) {
                        $("<ul />").appendTo(obj.np);
                    }
                    obj.np.children("ul:eq(0)").append(o);
                }

                try {
                    obj.ot.clean_node(obj.op);
                    obj.rt.clean_node(obj.np);
                    if (!obj.op.find("> ul > li").length) {
                        obj.op.removeClass("jstree-open jstree-closed").addClass("jstree-leaf").children("ul").remove();
                    }
                } catch (e) {
                }

                if (is_copy) {
                    prepared_move.cy = true;
                    prepared_move.oc = o;
                }
                this.__callback(prepared_move);
                return prepared_move;
            },
            _get_move : function() {
                return prepared_move;
            }
        }
    });
})(jQuery);